package no.sachmuld.methodcombination.api;

import org.junit.Assert;
import org.junit.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 *
 */
public abstract class StandardTestTemplate {


    @Test
    public void shouldExecuteBehaviourStandardMethodStyle() throws Exception {
        List<Step> expected = Arrays.asList(Step.AROUND_Y_BEGIN, Step.AROUND_X_BEGIN, Step.BEFORE_Y, Step.BEFORE_X, Step.Y, Step.X, Step.AFTER_X, Step.AFTER_Y, Step.AROUND_X_END, Step.AROUND_Y_END);
        final List<Step> actual = new ArrayList<>();
        Foo foo = createFoo();
        foo.with(new FooBehaviour() {
            @Override
            public Result aroundBar(MethodInvocation<Result> methodInvocation) {
                actual.add(Step.AROUND_X_BEGIN);
                try {
                    return methodInvocation.proceed();
                } finally {
                    actual.add(Step.AROUND_X_END);
                }
            }

            @Override
            public void beforeBar() {
                actual.add(Step.BEFORE_X);
            }

            @Override
            public Result bar(MethodInvocation<Result> methodInvocation) {
                actual.add(Step.X);
                return null;
            }

            @Override
            public void afterBar() {
                actual.add(Step.AFTER_X);
            }
        });
        foo.with(new FooBehaviour() {
            @Override
            public Result aroundBar(MethodInvocation<Result> methodInvocation) {
                actual.add(Step.AROUND_Y_BEGIN);
                try {
                    return methodInvocation.proceed();
                } finally {
                    actual.add(Step.AROUND_Y_END);
                }
            }

            @Override
            public void beforeBar() {
                actual.add(Step.BEFORE_Y);
            }

            @Override
            public Result bar(MethodInvocation<Result> methodInvocation) {
                actual.add(Step.Y);
                methodInvocation.proceed();
                return null;
            }

            @Override
            public void afterBar() {
                actual.add(Step.AFTER_Y);
            }
        });

        foo.bar();

        Assert.assertEquals(expected, actual);

    }

    @Test
    public void shouldReturnValueFromMostSpecificPrimaryMethod() throws Exception {
        Result expected = Result.B;
        Foo foo = createFoo();
        foo.with(new FooBehaviour() {
            @Override
            public Result bar(MethodInvocation<Result> methodInvocation) {
                return Result.A;
            }
        });
        foo.with(new FooBehaviour() {
            @Override
            public Result bar(MethodInvocation<Result> methodInvocation) {
                return Result.B;
            }
        });

        Result actual = foo.bar();

        Assert.assertEquals(expected, actual);

    }

    @Test
    public void shouldAcceptMissingAuxiliaryMethods() throws Exception {
        Result expected = Result.A;
        FooWithNoAuxilliaryMethods foo = createFooWithNoAuxilliaryMethods();
        foo.with(new FooBehaviourWithNoAuxilliaryMethods() {
            @Override
            public Result bar(MethodInvocation<Result> methodInvocation) {
                return Result.A;
            }
        });
        Result actual = foo.bar();
        Assert.assertEquals(expected, actual);

    }

    static public enum Step {
        AROUND_X_BEGIN, AROUND_X_END, BEFORE_X, X, AFTER_X, AROUND_Y_BEGIN, AROUND_Y_END, BEFORE_Y, Y, AFTER_Y
    }

    static public enum Result {
        A, B
    }

    static public abstract class FooBehaviour {
        public Result aroundBar(MethodInvocation<Result> methodInvocation) {
            return methodInvocation.proceed();
        }
        public void beforeBar() {}
        public Result bar(MethodInvocation<Result> methodInvocation) {
            return methodInvocation.proceed();
        }
        public void afterBar() {}
    }

    static public abstract class Foo extends MethodCombinationSupport<FooBehaviour> {
        public abstract Result bar();
    }

    static abstract public class FooBehaviourWithNoAuxilliaryMethods {
        public Result bar(MethodInvocation<Result> methodInvocation) {
            return methodInvocation.proceed();
        }
    }

    static public abstract class FooWithNoAuxilliaryMethods extends MethodCombinationSupport<FooBehaviourWithNoAuxilliaryMethods> {
        public abstract Result bar();
    }

    protected abstract Foo createFoo();
    protected abstract FooWithNoAuxilliaryMethods createFooWithNoAuxilliaryMethods();
}
